package com.linhngua.code.jaxb;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;

import com.sun.xml.bind.marshaller.CharacterEscapeHandler;

public class JAXBProcessor<T> {
	
	private static final String	CHARACTER_ESCAPE_PROPERTY	= "com.sun.xml.bind.characterEscapeHandler";
	
	/**
	 * To marshal a bound object to XML
	 * 
	 * @param marshalledObject
	 * @param context
	 * @return
	 * @throws JAXBException
	 */
	public String toXml(T marshalledObject) throws JAXBException {

		return toXml(marshalledObject, true);
		
	}
	
	public String toXml(T marshalledObject, boolean escape)
			throws JAXBException {

		String context = marshalledObject.getClass().getPackage().getName();
		Marshaller marshaller = getMarshaller(context, escape);
		
		StringWriter stringWriter = new StringWriter();
		marshaller.marshal(marshalledObject, stringWriter);
		return stringWriter.toString();
		
	}
	
	public String toXmlForNonRoot(T marshalledObject, QName qName)
			throws JAXBException {

		return toXmlForNonRoot(marshalledObject, qName, true);
	}
	
	@SuppressWarnings("unchecked")
	public String toXmlForNonRoot(T marshalledObject, QName qName,
			boolean escape) throws JAXBException {

		String context = marshalledObject.getClass().getPackage().getName();
		Marshaller marshaller = getMarshaller(context, escape);
		StringWriter stringWriter = new StringWriter();
		
		JAXBElement<T> element = new JAXBElement<T>(qName,
				(Class<T>) marshalledObject.getClass(), marshalledObject);
		marshaller.marshal(element, stringWriter);
		return stringWriter.toString();
	}
	
	private void setNotEscape(Marshaller marshaller) throws JAXBException {

		marshaller.setProperty(CHARACTER_ESCAPE_PROPERTY,
				new CharacterEscapeHandler() {
					
					@Override
					public void escape(char[] ac, int i, int j, boolean flag,
							Writer writer) throws IOException {

						// do not escape
						writer.write(ac, i, j);
					}
					
				});
	}
	
	private Marshaller getMarshaller(String context, boolean escape)
			throws JAXBException {

		JAXBContext jaxbContext = JAXBContext.newInstance(context);
		Marshaller marshaller = jaxbContext.createMarshaller();
		
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, new Boolean(
				true));
		if (!escape) {
			setNotEscape(marshaller);
		}
		
		return marshaller;
	}
	
	@SuppressWarnings("unchecked")
	public T toBoundObject(String xml, Class<T> context) throws JAXBException {

		JAXBContext jaxbContext = JAXBContext.newInstance(context.getPackage()
				.getName());
		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
		StreamSource streamSource = new StreamSource(new StringReader(xml));
		Object o = unmarshaller.unmarshal(streamSource);
		if (o instanceof JAXBElement) {
			JAXBElement<T> element = (JAXBElement<T>) o;
			return element.getValue();
		}
		else {
			return (T) o;
		}
		
	}
	
	@SuppressWarnings("unchecked")
	public T toBoundObjectForType(String xml, Class<T> context) throws JAXBException {

		JAXBContext jaxbContext = JAXBContext.newInstance(context.getPackage()
				.getName());
		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
		StreamSource streamSource = new StreamSource(new StringReader(xml));
		Object o = unmarshaller.unmarshal(streamSource,context);
		if (o instanceof JAXBElement) {
			JAXBElement<T> element = (JAXBElement<T>) o;
			return element.getValue();
		}
		else {
			return (T) o;
		}
		
	}
	
	@SuppressWarnings("unchecked")
	public T toBoundObject(InputStream inputStream, Class<T> context)
			throws JAXBException {

		JAXBContext jaxbContext = JAXBContext.newInstance(context.getPackage()
				.getName());
		Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
		Object o = unmarshaller.unmarshal(inputStream);
		if (o instanceof JAXBElement) {
			JAXBElement<T> element = (JAXBElement<T>) o;
			return element.getValue();
		}
		else {
			return (T) o;
		}
		
	}
	
}
